Skip to content

Multiaxis kinematics and limits - #4374

Draft
grandixximo wants to merge 74 commits into
LinuxCNC:masterfrom
grandixximo:multiaxis-kins-limits
Draft

Multiaxis kinematics and limits#4374
grandixximo wants to merge 74 commits into
LinuxCNC:masterfrom
grandixximo:multiaxis-kins-limits

Conversation

@grandixximo

@grandixximo grandixximo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Draft. Where the multiaxis kinematics work is going, open early so it can be argued with. As pieces are agreed I split them into small PRs, rebase this, and carry on, so nothing stalls behind it.

Plan, in order:

  1. Switchkins: one implementation, used by .comp, .c, in tree and out of tree. The registration half merged as switchkins: allow more than three kinematics types #4372; the rest is in this branch.

  2. Small fixes that stand on their own: 5axiskins gets a .tool-offset pin like every other module, maxkins gets one or a doc note saying it has none, and G53 stops ignoring the rotary tool offset on non-wrapped axes.

  3. The kinematics interface learns to answer two more questions: where the tool points, and how joint motion maps to world motion. One entry point each, both weak, both defaulting to today's behaviour, and a tool frame convention written down so every module answers the same way rather than each author choosing one.

  4. Those answers, and forward and inverse alongside them, become pure functions of what the caller passes in: joint values, a parameter block carrying the tool and the kinematics type, and no state kept inside the module. Today the maths reads its geometry from HAL pins and keeps scratch in haldata, so the only way to call it from anywhere else is to write a second copy of it. One implementation, parameters supplied at the call site.

  5. Two stages the offset chain was missing. Both came out of the discussion below, both wanted 3 and 4 first, and both are in this branch now.

    A frame composed on top of the active coordinate system, with its own cancel, so the blocks in between are programmed in the tilted plane while G54 itself is untouched. Fanuc spells this G68.2, and that is the spelling here: G68.2 in its four forms, G68.3 reading the plane off the tool, G68.4 composing onto the active plane, G69 cancelling. In tree it is a stage in rotate_and_offset_pos() plus modal group 9. Nothing is rewritten, so nothing needs restoring: any abort cancels it, and on_abort_with_twp_reset.ngc is gone with the rest of the remap.

    A move whose target is defined but whose path is left to the joints: run the inverse once at the endpoint, then interpolate the joints to reach it, the slowest setting the pace. That is how the commercial controls cross a singularity or flip a head without changing coordinate system. In tree it is one segment type in the planner, JOINT_TRAVERSE and JOINT_FEED in canon, and three non-modal codes on G0 and G1, like G53, each stripping one more layer of interpretation from the destination: G53.4 to a program point, offsets and plane applied, the form a post writes; G53.5 to the slides, the same axis letters read as though the kinematics were the identity, in program units and with no offset, the form for parking and tool change; G53.7 to joint values, J2=-5 sending joint 2 to -5 in the joint's own units with nothing converted, the form that works on every machine. A letter carries a unit class and a joint does not, so G53.5 is refused where the letters name joints of the other kind, which [JOINT_n] TYPE declares and a serial robot fails on X. With G1 the move takes the time the straight move would at F. G53.1, G53.3 and G53.6 orient the tool to the plane, P picking the solution nearest first and Q saying whether the joints that carry the work take part; the module's tool frame inverse answers, so no config carries the formula, and the two trsrn configs run their demos with no remap left.

    Between them these are the reason not to add a set of offsets per kinematics type. Controls do let you add coordinate systems, Fanuc G54.1 P1 to P48, Siemens G505 to G599, and none of them ties a set to the active kinematics or transformation: selection is explicit in the program, or it belongs to a channel. More coordinate systems is a fair request on its own; binding them to the kinstype is the workaround for these two absences.

  6. Limits taken from the joints instead of from static per-axis INI values. Joint limits are the physical fact. The region they imply in axis space depends on the tool and on where the rotaries are, and is not a box, which is why a box in the INI cannot describe it and config authors have to pick a conservative one and lose travel. [AXIS_L] keeps the job it is good for, a restriction the user imposes in axis space, normally set inside what is reachable; the effective bound is the intersection of the two, and on non-trivial kinematics neither one contains the other, so both are checked. Canon asks per move, which gives TP0 and TP1 the result with no planner change and costs 28 us a move on a closed-form five-axis module. Every commercial five-axis control applies its limits in machine-axis space after the transformation; this is the piece LinuxCNC does not have. Asking also makes the limits per kinematics type, which retires a workaround the docs currently prescribe: switchkins.adoc says the INI limits apply to type 0 only and tells you to setp ini.L.min_limit from M-codes at each switch, with the puma sim shipping M128, M129 and M130 to do it by hand. The realtime seam and TP2 after, and only with an analytic Jacobian: differentiating numerically is eighteen inverse calls, 2 us on a closed-form module and milliseconds on an iterative one.

    The same query answers a smaller defect the tool orientation codes show today. G53.1 ranks the poses the module offers by raw joint difference, so a rotary that carries many turns of travel goes the long way round: a C at 180 asked for a pose at C -90 turns 270 degrees, where C 270 is the same place and 90 degrees away. Unwrapping a rotary solution towards where the joint is has to know that joint's range, which is what this item supplies; until then the interpreter has no joint limits to check against and cannot tell the machine that may take the extra turn from the one that may not.

    This item is travel only. [AXIS_L] also carries MAX_VELOCITY and MAX_ACCELERATION, which have the same defect a static per-axis scalar always has here: it cannot describe a bound that depends on where the rotaries are. Those come off the Jacobian in 3 rather than off the reachability query in this item, and are a stage of their own.

  7. The joint limit check moves earlier and samples inside segments. It exists already, in inRange(), but only at move endpoints and only once the block has reached motion, so a program that will overtravel says so partway into the cut rather than before it starts. With 4 in place the same check runs at load time, against the tool and the kinematics type each block will actually use. A verify button in a GUI is then a presentation of that, not a subsystem of its own.

    Two things this needs pinned before it is written. The interpreter has no seam for the query: all 66 GET_EXTERNAL_* accessors take at most an index, an axis, a spindle, a pocket, and never geometry, so one taking a segment is a new shape for that interface. And a load-time answer can go stale, since the interpreter runs ahead and motion.switchkins-type is a HAL_IN pin, so until 9 lands the type a block is checked against is not necessarily the type it runs under.

    Where a module cannot answer, cheaply or at all, the check skips rather than refuses. Refusing a reachable program leaves the user no recourse; missing an unreachable one is still caught by inRange() at runtime. That asymmetry decides every ambiguous case in 6 and 7.

    Checking a program that switches type raises a question the tree currently leaves implicit: whether a type's coordinates are geometric. Most are, and for those a scan is one stream of world points with only the inverse changing at the switch. identity is not, since world becomes the joints, so a datum in millimetres lands on a rotary joint in degrees; nothing notices, which is why switchkins.adoc asks the operator to clear the offsets before switching.

    The joint-space syntax that question wanted is G53.7 above: joint values by joint number, no offsets, the joint limits directly. Whether a type should also declare its space for the scan stays open.

  8. Per-mode restrictions. G28, G30, G53, G92, G52 and M6 do not mean the same thing in every kinematics type, and today each one quietly does whatever the module happens to leave it doing. Refusing outright beats guessing, and with 4 in place the control knows enough to decide rather than to be told.

  9. Retire motion.switchkins-type, once the grace period has run. Which release that is has still to be decided.

G12.1 and G13.1 are #4414, split out of this branch and standing on their own.

On tool length specifically: the offset is a nine-axis pose from the interpreter, canon adds it into the coordinate, and each rotary kinematics takes the Z component back through a HAL pin and re-applies it along the real tool direction. That round trip is exact when the config wires the pin, and silently wrong otherwise: a missing net costs the full tool length, and an X or Y offset is applied in the rotating work frame while the tool is bolted to the machine. The mechanism works; it has no owner.

Why 4 is worth the trouble: every other way of answering "will this program overtravel" builds a second model of the machine, in the verification package or in the post, and the two descriptions drift apart. If the kinematics module can be called with parameters the caller chooses, LinuxCNC can check a program against the same code that will run it, with no second model to keep in sync. That is not available to anyone whose kinematics is locked inside the control.

In this branch now

Items 1, 3, 4 and 5, in that order, 63 commits on master:

  • Switchkins is one implementation: the dispatch splits from rtapi_app_main(), the four comps call it from EXTRA_SETUP(), switchkinscomp.comp is the out-of-tree template, and the implementation installs as source. G12.1, G13.1: select the kinematics type from G-code #4414 (G12.1 and G13.1) sits at the bottom.
  • Item 3: every module reports its work frame and its tool frame, 5axiskins included, which reported none until now and so refused every orientation code, the tool frame inverse and the Jacobian are shared code with closed forms registered where a module has one, and the conventions chapter fixes frames, signs, tool X and the zero pose. kinematics: let a module report where the tool points #4455 sits in the middle.
  • Item 4: a module is a parameter block and a table of pure functions; kinematics_userspace/ loads a module outside RT and binds to the RT instance's live pins; tests/kins-params checks that each module answers the same outside RT.
  • Item 5: the frame, the point-to-point moves, the orientation codes and G68.3, as above, with the interpreter evaluating the module ahead of motion. The inverse ahead of motion runs to a fixed point, because a module may read the joints it is handed and one pass from a stale seed answers for the wrong rotary angles. In motion a point-to-point segment keeps the last forward solution and clears a status flag when the forward fails inside it rather than parking the reported position at zero, and an external offset waits while a point-to-point segment is queued, since its joints were solved without it.

User visible

Ten G-codes, all of them non-modal except the plane itself, which has its own cancel:

code what it does
G68.2 define a tilted work plane on top of the active coordinate system, by Euler angles, by angles about fixed axes, by three points or by two vectors
G68.3 define one from where the tool points now
G68.4 compose one onto the plane already active
G69 cancel the plane
G53.1 turn the rotaries until the tool is normal to the plane, the linear joints staying where they are
G53.3 the same, and take the tool to an X Y Z given in the plane
G53.6 the same, holding the tool centre point where it is
G53.4 point-to-point to a destination in program coordinates, offsets and plane applied
G53.5 point-to-point to the slides, the same axis letters read through the module's identity mapping, in program units and with no offset
G53.7 point-to-point to joint values, J2=-5 naming joint 2 in its own units

P on the orientation codes picks among the poses that reach the plane, nearest first, and Q1 frees the joints that carry the work to take part. The point-to-point codes are modifiers of G0 and G1, and with G1 the move takes the time the straight move would at the programmed feed. J<n>=<value> is a new word form, read only where G53.7 uses it.

Alongside them:

  • stat.g68_offset, stat.g68_rotation and stat.g68_active in the python interface; halui's relative pins and the AXIS DRO apply the plane.
  • The four components gain the coordinates= module parameter; millturn loses its in and out example pins.
  • The two trsrn_twp configs run on the native codes: no remap, no python, no twp-status pin. Their python maths is the oracle in tests/kins-twp.
  • No existing pin, G-code or INI name changes.

Testing

tests/kins-params, tests/kins-jacobian, tests/tool-frame, tests/kins-tool-offset, tests/kins-twp and tests/twp-native cover the new code; the last runs the xyzacb nutating head through the plane, the orientation codes and the point-to-point moves against the python maths, and checks that every point-to-point move keeps its joints on one line in joint space, all together, so a wrong interpolation fails three of its checks. tests/ptp-robot puts pumakins through the same codes, where G53.5 is refused and G53.7 is the form that works. The four comp sim configs give identical positions to master through the same MDI sequence, in every kinematics type.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from 9e1c9ed to 773c3e9 Compare August 12, 2026 07:22
Comment thread src/emc/kinematics/switchkins.h Outdated
Comment thread src/emc/motion/motion.h
Comment thread src/emc/nml_intf/emc_nml.hh Outdated
@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch from 773c3e9 to 34ab07b Compare August 12, 2026 08:34
Comment thread docs/src/man/man9/motion.9.adoc Outdated
Comment thread src/emc/kinematics/switchkins.c Outdated
@BsAtHome

Copy link
Copy Markdown
Contributor

What is the migration path for people having their own out-of-tree kinematics switch component?

@grandixximo

grandixximo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

What is the migration path for people having their own out-of-tree kinematics switch component?

Their module needs no source change. G12.1 and G13.1 reach a module through the same kinematicsSwitch() the pin does, so a component that is switchable today is driven by G12.1 today without knowing G12.1 exists.

I checked rather than assumed. I built an out-of-tree component that deliberately does not use switchkins.h or switchkins.c: its own switchkins type, its own kinematicsSwitch(), its own kinstype pins, the shape somebody would have written before there was a supported way to reach the in-tree code. Against this branch:

G12.1 P1   kins-type=1, its type 1 kinematics active
G13.1      kins-type=0
G12.1 P7   its own BAD VALUE, motion stays on 0

with no source change, and no deprecation warning, since the pin is never written.

The config keeps working as well. The pin is deprecated, not gone, so an existing M68/M66 config runs as before with one warning per session. Moving off it is a config edit, not code:

M68 E3 Q1 / M66 E0 L0    ->   G12.1 P1
M68 E3 Q0 / M66 E0 L0    ->   G13.1

and the [HAL] net from the analog output goes with them. The sim configs in this PR are worked examples, halui buttons and remapped M-codes included.

One group does have something to do, and it is not the one you asked about: anybody keeping a copy of switchkins.c in their own tree. Their current build is unaffected, but rtapi_app_main() has moved out to switchkins_main.c, so next time they re-sync that copy they either take that file too or call switchkinsInit() themselves. They also no longer need the copy, since switchkins.h is installed now and switchkinscomp.comp shows both routes.

Announcing it is worth raising at a weekly meeting. Nothing is removed in this PR.

Comment thread docs/src/gcode/g-code.adoc Outdated
Comment thread src/emc/kinematics/5axiskins.c Outdated
Comment thread src/hal/components/switchkinscomp.comp
@Sigma1912

Copy link
Copy Markdown
Contributor

Just a thought:
Work offsets in one kinematics are usually not applicable in another one. So when using switchable kinematics I have often found it would be nice to have different sets of G54..G59 offsets.
Maybe a parameter offset set by the 'motion.kins-type' value?

@grandixximo

Copy link
Copy Markdown
Contributor Author

That matches what switchkins.adoc already warns about: coordinate system offsets are described as generally applicable only to the type 0 startup kinematics, with advice to reset them or update them before switching. So the problem is real, and today it rests on the operator remembering.

Can I ask what you switch between, and whether you cut in both types? I am trying to tell two situations apart. If both types leave the axis letters describing the same physical point, then your G54 values are still correct after the switch, and what is missing is having a different one become active. If one of the types is identity, the stored numbers stop describing anything, since X becomes joint 0 and may not even carry the same units, and then a different set does not help either; the values want clearing.

Which of these is it for you: does the zero point of the part sit somewhere different once you switch, or is the part zero the same and you simply want a different one of G54 to G59.3 selected?

On the mechanism, do you mean an association living in the INI, a coordinate system named per kinematics type? There is nothing like that today. Or do you mean it read at runtime? kins-type is a status pin, so it reports rather than commands, but you could net it into a motion analog input and branch on it with M66 in a sub, which would work now. I am unsure whether that belongs in the core or stays the sort of thing an integrator wires up, and your answer would tell me which problem I am actually looking at.

@Sigma1912

Copy link
Copy Markdown
Contributor

Which of these is it for you: does the zero point of the part sit somewhere different once you switch, or is the part zero the same and you simply want a different one of G54 to G59.3 selected?

Here are two examples, the tool may cut in all kinematic modes.

Example1: Serial manipulator arm
https://github.com/LinuxCNC/linuxcnc/tree/master/configs/sim/axis/vismach/melfa-sim
To overcome singularities in industrial robots we need to be able to switch between joint and cartesian mode inside a gcode program. Here the values as well as the units change from one kinematic mode the other.

Example2: Tilted work plane
https://github.com/LinuxCNC/linuxcnc/tree/master/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating
The switch to TOOL/TWP mode requires a transformation of the offset values from one coordinate frame to another (rotated AND translated). The new work offset (that describes the same point on the work piece) has thus different values that must be activated after the switch. The original offsets must be restored after switching back.

Currently I have two ways of dealing with this:

  1. Keep the offsets in separate WCS systems (eg use G54 for one and G59 for the other mode). Switching from one to the other is easily handled in the custom M command used to switch the kinematics

  2. Stash the offset values in parameters (so they can be restored when switching back) and then overwrite the old ones.
    This can also be handled in the custom M command but requires much thought to ensure that the correct offset values are restored in all circumstances. Generally I VERY much dislike to alter values in the offset table in the background. There is a lot that can go wrong (eg in case of an abort)

On the mechanism, do you mean an association living in the INI, a coordinate system named per kinematics type? There is nothing like that today. Or do you mean it read at runtime? kins-type is a status pin, so it reports rather than commands, but you could net it into a motion analog input and branch on it with M66 in a sub, which would work now.

Maybe a different set of G54...G59.3 values. One set for each kins-type.
I have also thought about having G54...G58 for kins-type 0, G54.1 ... G58.1 for kins-type 1, G54.2 ... G58.2 for kins-type 2 and so on (for backwards compatibility G59..g59.3 maybe the same for all)

There are also some other things to think about when working in different kinematic modes ( eg G28 , G30, G53, G92/G52, M6) it might be useful to have way of blocking certain Gcodes / Axiswords in a given mode.
I have made a patch that allows blocking certain axis letters at runtime using a magic-comment. (Just as an illustration, not suggesting to use it)
ca9c334

@grandixximo

Copy link
Copy Markdown
Contributor Author

Both examples are clear and they moved my view: I had been assuming identity was mostly a jogging mode, and you cut in all modes. I looked at how the commercial controls handle both cases, and in each of them the closest thing we could do in LinuxCNC turns out not to be an offset set.

For the singularity case, the target stays in Cartesian coordinates and only the interpolation changes: a move that runs the inverse once at the endpoint and then interpolates the joints linearly to reach it. Coordinates and units never change, so the offsets keep applying and there is nothing to switch. In tree that is a new move type from canon into the trajectory planner, and the existing KINEMATICS_INVERSE_FLAGS already carry enough to pin which solution branch it lands on. G-code would need a way to ask for it.

For the tilted plane, an extra stage in the offset chain rather than new values in it: a frame composed on top of the active G54, with its own cancel, so the blocks in between are programmed in the tilted plane while G54 itself is untouched. That is a stage in rotate_and_offset_pos() plus the modal state to go with it. Nothing is rewritten, so nothing needs restoring and on_abort_with_twp_reset.ngc has nothing left to do. Orienting the tool normal to the plane, your G53.1, does need the kinematics to say where the tool points and to solve for the rotaries, which is item 3 above.

On having more of them: that part has plenty of precedent and I would be happy to look at it separately. Fanuc adds G54.1 P1 to P48, or to P300 on the 30i, and Siemens adds G505 to G599. What I cannot find anywhere is a control that ties a set to the active kinematics or transformation. Selection is always explicit in the program, or it belongs to a channel or path. So more coordinate systems yes, but I would rather not bind them to the kinstype.

Which leaves me reading one set per kinstype as paying for the two absences above rather than as the thing you actually want. You have run this in production and I have not, so I would rather hear where that is wrong. Would a composing frame cover your case 2, or would it miss something?

Your other point stands either way. Per-mode restrictions on G28, G30, G53, G92 and M6 look right to me, and refusing outright beats guessing.

@Sigma1912

Copy link
Copy Markdown
Contributor

Nice that you have an overview of what other controllers are doing. Makes more sense adopting something that is established rather than coming up with something quirky that falls on our feet somewhere down the road.
My big deficit (or at least one of them) is that I often have zero experience how things work
on commercial machines so I end up reinventing the wheel and often do it in a slightly odd manner because I don't really know what I'm doing. :)

Which leaves me reading one set per kinstype as paying for the two absences above rather than as the thing you actually want. You have run this in production and I have not, so I would rather hear where that is wrong. Would a composing frame cover your case 2, or would it miss something?

That is how I started out, an offset built into the kinematics. The reason I moved away from that was that in it's current form LinuxCNC is totally unaware of what is going on in a custom kinematics.
So if we can handle things like G53 etc. internally then I'm probably happy with not having different sets of offsets.

Basically what I'd like is to NOT have to tamper directly with the values in the offset table.

@grandixximo

Copy link
Copy Markdown
Contributor Author

That is the same conclusion from the other side, and it names the dependency. A composing frame is only worth having if motion knows the frame is there; otherwise G53 and G28 keep meaning whatever each module happens to do with them, and we are back to the operator remembering. That is items 3 and 4 above, which is why they come before anything in the offset chain.

What "aware" has to mean concretely: the module answers where the tool points and how joint motion maps to world motion, both as plain functions of the joint values and a parameter block, with no state hidden in HAL pins. Once the control can call the kinematics rather than only drive it, G53 resolves in machine axis space, the per-mode restrictions you listed get checked rather than guessed, and a frame composes on top of G54 with nothing rewriting the table.

So no per-kinstype offset sets, nothing tampering with the table in the background, and the tilted plane becomes a frame that cancels. Your two examples are the cases I will hold it to.

@Sigma1912

Copy link
Copy Markdown
Contributor

Sounds great!

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from ffd07b8 to fea53f7 Compare August 19, 2026 09:51
@grandixximo

Copy link
Copy Markdown
Contributor Author

One more thing the tool round trip does not cover, found while giving 5axiskins and maxkins the .tool-offset pin every other rotary module has.

Both modules add the W coordinate to the pivot to tip distance, so W is already a tool-length route: G43.1 W5 followed by a block that commands W0 puts the kinematics on the right radius and lands on the same joint solution as a Z-column offset does. It is easy to miss, because G43.1 W5 on its own changes nothing. convert_tool_length_offset() pushes w_current down by the offset, canon adds it back, and a block with no W word hands the module zero. Command W once and it is correct from there on.

So on these two modules the tool length has three spellings: the W column with W commanded, the sum2 into pivot-length that the bridge mill sim uses, and now a .tool-offset pin. They all compute the same radius and they all add, so using two double counts. None of them is written down anywhere, which is the actual problem.

This is the same shape as the rest of items 3 and 4. Each module infers the tool from whatever it happens to be handed, every config picks a different one of the three, and nobody owns which is meant. Once the tool arrives as a parameter at the call site there is one answer instead of three.

@Sigma1912

Copy link
Copy Markdown
Contributor

I have a set of kinematics that probably cover most 5 axis machine (tool side rotation, work side rotation and mixed) including nutating joints and tilted work plane. I have long wonder if I should add those to the collection:
https://github.com/Sigma1912/LinuxCNC_Demo_Configs/tree/main/5axis-twp

@grandixximo

Copy link
Copy Markdown
Contributor Author

Yes, and I would take the kinematics first.

The nutating ones are what items 3 and 4 need most. A nutating head is where "where does the tool point" stops being obvious, so a weak default every module answers is only worth having if something in tree can break it. Today the only nutating configs are the two trsrn ones, which are yours already.

On the TWP remap I would rather you decide with item 5 in front of you. I am proposing to move that mechanism into rotate_and_offset_pos() as a frame that composes on top of G54 and cancels, which would leave the Python doing less or nothing. That does not make the configs useless, the opposite: they are the executable spec for what the frame has to do, and the baseline for checking whether a C version matches. But adding them as tree configs while I am proposing to replace the mechanism should be a decision rather than a side effect. I would rather have them in than not, and you know the ground better than I do.

One request if the kins come in: could they take the tool length through a .tool-offset pin the way the trt modules do? I have just been through this on 5axiskins and maxkins and found three spellings of the same quantity with none of them written down. Three more modules each picking their own makes that worse, and settling it now is cheap.

@Sigma1912

Copy link
Copy Markdown
Contributor

I'm not in hurry it's been sitting here for a while now.
I have no problem making adjustments to make it fit better.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from d32027c to 87c645b Compare August 22, 2026 08:00
@grandixximo

grandixximo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

I had a go at the split on the two trsrn configs in tree, to see how it lands: https://github.com/grandixximo/linuxcnc/tree/twp-remap-split

Your generic remap.py plus a remap_funcs_twp.py beside each config. Your two snrtr files carry exactly the two branches the tree had inline, xyzacb-snrtr is the (C, A) maths and xyzbca-snrtr is the (C, B) maths, so it was mostly substitution. Adapted for the tree: ini read through linuxcnc.ini, kinematics switched with G12.1, and kins_set_values converts the two joint angles to degrees because the modules in tree read those pins in degrees. Pin names unchanged, so neither .comp is touched.

I ran both configs through eight orientations under G53.1 P0/P1/P2, G53.3, G53.6 and G68.3 before and after, and it looks right: nothing that worked stopped working, and P1, P2, G53.6 and G68.3 now do something where they used to fail or silently no-op.

Three things I hit in your files:

xyzbca-snrtr has the wrong default tool-x. Both files aim for the same thing in the comment, the oriented x-vector parallel to the machine xy-plane. Tool x is the first column of Rp*Rs*Rz(tc), so that is entry [2][0] == 0, and since Rp is Rz only the bottom row of Rs enters:

bottom row [-Sv*Ss, t, s]  ->  tc = atan2(Sv*Ss, t)
bottom row [t, Sv*Ss, s]   ->  tc = atan2(-t, Sv*Ss)

xyzacb-snrtr has the second row and the second formula and is right. xyzbca-snrtr has the first row and the second formula, so it orients tool y horizontal instead. Looks copied from the sibling. Invisible in use, which is presumably why it survived: the tool still points where it was told to, only x and y in the tilted plane swap, so G68.3 R starts a quarter turn off. I used the tree's form in the port.

xyzacb_snrtr_kins.comp declares hal_float_t *virtual_rot; twice, lines 34 and 38, so it does not compile.

xyzacb-snrtr/remap_funcs_twp.py, kins_calc_primary, vertical tool branch sets theta_1 = 0 and then returns the empty list, so any G53.n to vertical fails. xyzbca-snrtr says return [0] there. I took return [0].

The commit is authored by me at the moment and the code is yours, so I would set you as author before it goes anywhere. Happy either way: take the branch and send it yourself, or tell me it looks right and I will.

@Sigma1912

Copy link
Copy Markdown
Contributor

Thanks for looking through this. I'll need to have a good look and report back.

@Sigma1912

Copy link
Copy Markdown
Contributor

I have found a problem with G13.1/G12.1 when either is in a 'on_abort' call.

Testing with configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp

This config has [RS274NGC]ON_ABORT_COMMAND = o <on_abort_with_twp_reset> call

I have noticed strange behavior with the remapped gcodes after issuing MDI commands that trigger this routine. For example, it looks like the g682 function gets stuck on yield INTERP_EXECUTE_FINISH

You can get an indication of the issue if you uncomment the first line in the on_abort sub:

o<on_abort_with_twp_reset> sub
(msg, on_abort START)    ; <<<< UNCOMMENT this line so the msg shows on start
M68 E2 Q0       ; reset twp-state to 'undefined' (0)
G13.1           ; back to identity kins
G64 P0.01       ; reset the toolpath tolerance as this sometimes gets set to zero on estop events
G54             ; switch to G54
(msg, on_abort END)
o<on_abort_with_twp_reset> endsub
M2

Now start the config, clear estop and switch the machine on which calls the on_abort routine. Note though that it does apparently NOT reach the end since we don't get the message on the last line:

on abort

Further testing has shown that having either a G13.1 or G12.1 P0 in the on_abort routine triggers the problem while the former M68 E3 Q0 does not.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch from 87c645b to 5bf0103 Compare August 23, 2026 14:11
A frame composed inside the offset chain, so that the blocks between a definition and its cancel are programmed in the tilted plane while G54 itself is untouched:

    world = TLO + G5x + Rz(rotation_xy) * (G92 + O + R * program)

O and R are the plane's origin and rotation in the coordinate system that was active when the plane was defined. Rotary and UVW words do not pass through it.

G68.2 takes the Fanuc forms: three angles about the axes Q names (P0 Euler, ZXZ by default; P1 about the fixed axes, XYZ by default), three points over four blocks (P2), two vectors over two blocks (P3), each with an R turning the plane about its own Z. G68.4 composes any of those onto the active plane. G69 cancels. The plane is not persistent: init, M2 and M30 clear it as well.

An abort cancels the plane, and tells canon so even when the read ahead had already cancelled it. What status reports is the plane the executed canon stream last set, and the read ahead reaches a program's own G69 long before the machine does, so that cancel can still be in the queue when an abort throws the queue away: the interpreter is then right and status still shows a plane, and a G69 typed afterwards finds nothing to cancel and says nothing to canon. An explicit G69 now tells canon either way, so one can always settle the two.

Canon gains SET_G68_FRAME and the frame stage in rotate_and_offset_pos() and its inverse, so positions and probe results come back in plane coordinates and arcs tilt with the plane. Task status carries g68_offset, g68_rotation and g68_active next to the other offsets; the python canons, the AXIS display and halui's relative position pins apply it.

While a plane is active the interpreter refuses G92 and G52, G10 L2, L20, L10 and L11, and a change of coordinate system, since each of those defines the system the plane sits on.
…realtime

The loader gains the two frames, the tool frame inverse and a survey of which joints turn the work, all on kins_util.c code the library already carries; only the glue between the block form and the joint-only frame functions the solver takes is new. The survey is what a caller uses to hold the table still without a config entry naming it. A second survey tells the two rotaries that orient the tool apart, the primary whose axis is fixed in the machine frame from the secondary whose axis it carries, by turning each a little and reading the axis of the rotation that results; the sign of the secondary is how a caller names one of the two poses that reach a tool direction rather than counting them. It answers only for a machine with exactly two such rotaries, so a robot wrist gets nothing and says so. tests/kins-params checks the pair every module reports, and a module that swapped them would fail there. kinematicsUserInitString() takes the value of [KINS] KINEMATICS as the HAL file hands it to loadrt.
A segment whose endpoint is Cartesian but whose interpolation is in joint space: the inverse runs once, at the endpoint, when the move is queued, and the planner runs every joint from where the queue ends to there together, the slowest joint setting the pace. Its length is the joint space distance and the tightest per-joint velocity, acceleration and jerk limits are scaled onto it, so no joint exceeds its own. Nothing blends into or out of it. While it runs the servo thread takes the joints from the planner instead of inverting a position, and reports where the tool is from the forward kinematics. This is how a program crosses a singularity or flips a head without leaving its coordinate system.

The move is a rapid, with the rapid override, or a feed that is to take a given time, with the feed override; the joint limits cap either. The endpoint can also be given as joints, whose world position motion finds with the forward; the tilted work plane orientation moves use that form.

Canon JOINT_TRAVERSE and JOINT_FEED, NML EMC_TRAJ_JOINT_MOVE and EMCMOT_SET_JOINT_LINE carry it. The preview draws it as the straight move between its ends. The G-codes that ask for it come with the orientation codes.

While a joint segment runs the servo thread commands the planner's joints whatever the forward kinematics says: a forward that fails, as an iterating one can at the singularity the move is there to cross, leaves the last solved position reported and carte_pos_cmd_ok cleared rather than an unsolved pose committed. An external offset cannot ride on a joint segment, whose joints were solved without one: the queue refuses the segment while an offset is applied, and a request that arrives while one is queued is not planned until the last joint segment is done, so it ramps in at its own limits afterwards instead of landing as a step at the segment's ends. A queue reset, on abort or on running empty, forgets the joint-space bookkeeping along with the queue.
… G53.1 to G53.6, and G68.3

The interpreter evaluates the kinematics module ahead of motion through the loader in kinematics_userspace, opened on first use from [KINS] KINEMATICS and JOINTS on a HAL component of its own, so that G53.n can ask the tool frame inverse where the rotaries go, G68.3 can read the tool direction and the point-to-point codes can tell where their joints put the tool.

G53.4, G53.5 and G53.7 are the point-to-point moves, non-modal modifiers of G0 and G1 like G53: the path is interpolated in joint space, only the endpoint is defined. Each strips one more layer of interpretation from the destination. G53.4 takes the endpoint as a program point through the offsets. G53.5 takes the same axis letters and reads them as though the kinematics were the identity, so a letter names the joints the module's identity mapping gives it, a gantry pair taking one value, the values in program units and no offset applied. A letter carries a unit class and a joint does not, so G53.5 is refused on a machine whose letters name joints of the other kind, which [JOINT_n] TYPE declares and a serial robot fails on X. G53.7 takes joint values as J<n>=<value> words, a new form the lexer reads when the J value is followed by an equals sign: the joint number and the joint's own position in its own units, no conversion at all, a joint left out keeping its position, the two joints of a gantry pair required together with one value. That form works on every machine, which is why the letters get the earlier number and the joints the later one. With G1 the move takes the time the straight move would at the programmed feed, in G93 exactly 1/F minutes; a G94 feed needs a displacement to apply F to. The joint limits cap either. Whether the target is a point or joints, the inverse ahead of motion is run to a fixed point, because a module may read the joints it is handed, the nutating head its rotary angles, and one pass from a stale seed answers for the wrong angles; motion's handler does the same for the endpoint of a point-to-point move.

G53.1 moves the rotaries to the plane's normal with the linear joints where they are, point-to-point; G53.6 does the same holding the tool centre point, a Cartesian move; G53.3 X Y Z goes to a point in the plane with the tool oriented, point-to-point. P picks among the poses that reach the plane. Without it the machine takes the nearest, the shortest move, which depends on where the rotaries are standing. P1 and P2 name the pose instead, by the sign of the secondary rotary, the one whose axis the other carries, so a program reaches the same pose wherever it starts; the interpreter finds that joint by turning each rotary a little through the module's tool frame and seeing whose axis the other carries, so nothing is configured for it. That is the choice Heidenhain writes SEQ+ and SEQ-, and it replaces an ordinal by distance that named a different pose on every run. Q says whether the joints that carry the work take part: Q0 holds them and lets the head do it, falling back to everything free when nothing is reachable that way, which is Heidenhain COORD ROT and what the python remap did; Q1 frees them from the start, TABLE ROT, and with the plane's X requested as well a machine with the joints for it places that too. Which joints carry the work is read off the module's work frame, not declared.

G68.3 takes the plane from the tool: Z is the tool axis as the joints have it, X the default tool X of the conventions chapter, turned about the axis by R.

tests/twp-native runs the xyzacb nutating-head sim through all of it against the python maths, which moves from the two configs into tests/kins-twp as the oracle it is; the point-to-point moves are timed there and G53.5 and G53.7 are checked on the tilted head, in millimetres and in inches. tests/ptp-robot puts pumakins through the same codes: G53.7 moves the joints it names, G53.5 is refused with the joint that gives the machine away, and G53.4 still takes a Cartesian destination.

Checked by mutation, in failed checks: reversing the interpolation of tcGetJointPos(), so that a joint segment runs from its end to its start and the end-of-segment inverse snaps it back, fails 3 in tests/twp-native, one per sampled point-to-point move (G53.1, G53.3, G53.4 G0), where before the path assertion it failed none. The joint limit scaling with a jerk limit runs on every joint of the config; the default planner does not consume it, so a mutation there is not caught by this test.
The two trsrn_twp configs drop the python remap of G68.2, G68.3, G68.4, G69, G53.1, G53.3 and G53.6, its ngc wrappers, the abort handler that unwound it and the twp-status analog pin: the interpreter does all of it now, with nothing to restore on abort. The helper component that feeds the vismach model reads the plane from status. The demos select the TCP kinematics up front, since the orientation codes need it, and put kinematics 0 back at the end; square.ngc loses its G52, which a plane refuses.

The G-code chapter documents G0.1, G53.1, G53.3, G53.6, G68.2, G68.3, G68.4 and G69, the modal group table gains the work plane group, the python interface documents the three status fields, and the kinematics conventions chapter points its orientation inverse at the codes that call it.
The head carries the tool and nothing turns the work, so the work frame is
the machine frame and the tool frame is Rz(C) Ry(-B): the head turns about
machine z and the tilt is left handed about y, the module's own note 10.
Its third column is the tool axis the forward transform already uses, the
pivot vector reversed, pointing from the tip towards the holder, so the
module needs no native rotation.

Without them the module answered nothing about orientation, so G53.1, G53.3,
G53.6 and G68.3 refused on the bridgemill sim while every other five-axis
module in the tree took them.

tests/kins-frames takes the module, and gains two checks the other modules
did not need. A switchable module carries an identity type that supplies
frames of its own, so a module reporting none for the machine it models
passed on its neighbour's answers; the type the module models must now
supply them. And the W joint runs the tool out along its own axis, so the
tip moves along the tool axis reversed, which ties the reported frame to the
forward transform. Reversing the tilt in the frame fails that quill check,
reversing the head's turn fails the spin check, and dropping the frames
altogether fails the new one; the frame's own algebra stays a rotation
through all three.
@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch from ed9a019 to 54f0848 Compare September 8, 2026 05:02
@grandixximo

Copy link
Copy Markdown
Contributor Author

@Sigma1912 thank you for both, and for taking the time to drive it. Feedback like this is what keeps the work from feeling like effort poured into a void.

The stop is fixed on the branch, with a regression in tests/twp-native that runs a program with a plane, stops it mid-move and checks that the plane clears and that a G69 afterwards is accepted. Please give it a go on your machine.

On the P word you talked me out of what I built. It was an index over the poses that reach the plane, ordered nearest first from where the machine happened to be standing, so the same P2 could select a different pose on a different run. It now names the pose instead of counting: no P still takes the nearest, P1 and P2 name the two poses by the sign of the secondary rotary, the one whose axis the other carries, which is the joint that leans the head or the table each way. One plane on the nutating sim, from two different starting positions:

secondary rotary
P1, from either start 51.127
P2, from either start -51.127
no P 51.127 from one start, -51.127 from the other

Which rotary that is comes out of the module's own maths rather than a config entry: the interpreter evaluates the tool frame either side of each joint and the one whose axis is carried by the other is the secondary. A machine without exactly two such rotaries, a robot wrist, gets no labels and says so rather than guessing, and tests/kins-params checks the pair every module in the tree reports.

Naming the branch rather than counting is what the commercial controls do, so a program moved across reads the same way, and it is close enough to what your P1 and P2 asked for that a program written for the remap usually keeps its intent.

One thing still worth your view, since it is where the two differ. The remap's modes constrained the direction of travel and mine constrains the destination, so they part company when a rotary is walking towards its limit. Do you use that in practice, or is taking the short way round enough? The short way itself I am fixing separately: G53.1 ranks by raw joint difference, so a C at 180 asked for a pose at C -90 turns 270 degrees where 90 would do, and unwrapping that needs the joint limits, which is item 6.

@Sigma1912

Copy link
Copy Markdown
Contributor

The stop is fixed on the branch,

Yes, that works now, thanks.

Do you use that in practice, or is taking the short way round enough?

That is fine for me as my application has a manually operated swivel head. The reason I put that in was because I found it in a Mazak manual. As long as we are following some kind of precedent we should be on the right path.

One more thing, when testing 'configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp'. On startup there is an error message from the interpreter running on load however the program actually runs just fine:

Screenshot from 2026-09-08 06-44-34

@grandixximo

Copy link
Copy Markdown
Contributor Author

Yeah, I noticed that as well, I thought cause I had G54 way out somewhere, I will double check, thanks...

Every plane in the frame test was one rotation of ninety degrees about
a single axis, written four ways.  With two of the three angles zero,
composing about the frame as it turns and composing about the fixed
axes give the same matrix, so the difference between G68.2 P0 and P1,
which is the whole reason they are separate codes, went unobserved:
deleting the distinction and letting P0 compose the way P1 does passed
the test.

One plane at 25, 40 and -15 degrees instead, still written four ways,
with the three points and the two vectors derived from it so the forms
still have to agree on the same matrix.  G68.4 and the R word take
angles that are not right angles too, and G53 inside the plane now
lands somewhere that is not a permutation of the axes.

Mutations the test catches:
    G68.2 P0 composing the way P1 does, which the old plane did not
    the R word composed before the plane rotation rather than after
    the three-point form taking Y as X cross Z
@grandixximo

Copy link
Copy Markdown
Contributor Author

@Sigma1912 I traced the error, and it is because axis loads the gcode, before loading the postgui configuration, one way to fix, is to have the pivots set with the HALCMD in the .ini
but that is ugly and duplicates where the pivots live, and is confusing for new users, I would rather change axis, but that would be a separate PR, I will prepare that, and will see if it lands, other UI have similar issues.

@Sigma1912

Copy link
Copy Markdown
Contributor

Nice, thanks for figuring that out.

The chapter and the G-code reference described the two angle forms
exactly but never said whose conventions they are, so a reader porting
a program or a post had no way to tell whether they match, and a later
change had nothing to check itself against.  P0 is Heidenhain's PLANE
EULER, P1 its PLANE SPATIAL, the form numbering is Fanuc's, and Fanuc's
fifth form is not implemented.

Two places the behaviour differs from the control it borrows from, both
now stated: P1 and P2 refuse where a control offering SEQ falls back to
the nearer pose, and P names a pose only where a rotary turns the tool,
so a machine whose rotaries all carry the work has only the nearest
form.  The G53.1 section said the two poses lean the head or the table
opposite ways, which reads as though the tilting-table configurations
have a pose to name; they do not, and the interpreter tells them so.
@Sigma1912

Sigma1912 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Testing 'G53.7' on 'configs/sim/axis/vismach/melfa-sim/melfa-sim-three21' shows a bug:

  1. Start config and home machine
  2. MDI: g53.7 g0 j4=0 ; OK
  3. MDI: g53.7 g0 j4=90 ; OK
  4. MDI: g53.7 g0 j4= -10 ; OK
  5. MDI: g53.7 g0 j4=90 ; ERROR joint interpolated move on line 0 would exceed joint 3's limit

There seems to be an issue with the sign change. Looks like it wants to maybe twist the forearm (J3) 180° when it should really just move joint 4.
Screenshot from 2026-09-08 10-33-12

The line that reports how far the linear joints wandered through G53.1
names the worst sample, and there is no worst sample when they hold
still, which is what the test asks of them.  Start the report at the
first sample so that a clean run has something to print.
A point does not name one joint set: a robot wrist reaches it again with
the forearm turned half a revolution.  Both ends of a point-to-point move
read the joints back from the point, so both could pick the other set.

The interpreter now keeps its seed while it still explains where the
machine is, rather than inverting the point every time and taking what
comes back, which reported joints the machine is not standing in and had
the next G53.7 refused for exceeding a joint's limit.

Motion now holds the joints a joint interpolated segment ended on while
the planner stays at that point, rather than inverting again the next
cycle and undoing the move just made.

tests/ptp-robot drives a wrist joint through zero and back and checks the
joints not named stay where they are.
@grandixximo

Copy link
Copy Markdown
Contributor Author

@Sigma1912 fixed and pushed, with a regression in tests/ptp-robot. Please give it a go once CI is green.

@Sigma1912

Copy link
Copy Markdown
Contributor

Now 'G53.5' and 'G53.7' don't work at all. Fail with:

emc/task/emctask.cc 71: interp_error: the kinematics cannot invert the current position
the kinematics cannot invert the current position
ERR kI - compute_jinv (joints: 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000), (iterations=0)

The interpreter works out the joints behind a point by inverting it, and
had nothing to start the first inverse from but zeros.  A module that
answers the inverse by iterating cannot be started anywhere: genserkins
takes the Jacobian at the seed, and a serial arm with every joint at zero
stands in a singular pose, so on a robot every G53.5 and G53.7 was refused
with "the kinematics cannot invert the current position".

Canon now reports the joint positions, and the interpreter falls back on
them when its own seed no longer explains where the machine is.  They also
name the arm the machine is standing in, which the inverse of a point
cannot: a robot reaches the same point again with the elbow the other way
up.
@grandixximo

Copy link
Copy Markdown
Contributor Author

@Sigma1912 fixed and pushed as 5ce95b4cda.

The interpreter works out the joints behind a point by inverting it, and had nothing but zeros to start the first inverse from. genserkins takes the Jacobian at the seed, and a serial arm with every joint at zero stands in a singular pose, so it refused before it began. Canon now reports the joint positions and the interpreter falls back on them when its own seed no longer explains where the machine is. That also names the arm the machine is standing in, which inverting a point cannot: the same point is reached again with the elbow the other way up.

On melfa-sim-genser here, homed at 0 -90 0 0 90 0:

G0 G53.7 J4=80          joints  0.0000  -90.0010   0.0010  0.0010  80.0000   0.0010
G0 G53.7 J0=10          joints 10.0000  -90.0010   0.0010  0.0010  80.0000   0.0010
G0 G53.4 X300 Y0 Z400   joints -0.0009 -133.6644  56.0467  0.0012  67.6177  -0.0002
G0 G53.7 J5=45          joints -0.0009 -133.6644  56.0467  0.0012  67.6177  45.0000

One question, since I have not been able to test it: did you hit this in MDI only, or does loading a program that contains G53.7 fail the same way? The preview has no machine to ask for the joints, so it starts from world zero, which a serial arm cannot reach, and I expect it to refuse there too. If it does, that is a second fix and it needs a decision about what pose the preview starts from, so I would rather know before shaping it.

Two other things you may run into on that config, both separate from this: G68.3 answers "the kinematics reports no tool frame", because genserkins does not supply the work and tool frames yet, and G53.5 refuses the axis letters, because the INI declares the joints angular while X Y Z imply length, so G53.7 J0= is the way in.

@Sigma1912

Copy link
Copy Markdown
Contributor

did you hit this in MDI only, or does loading a program that contains G53.7 fail the same way?

Fails the same way.

Two other things you may run into on that config,

Noted, thank you.

A kinematics module calls rtapi_print() on its way through, and a GUI has
the userspace HAL library only underneath another shared object it loaded,
whose symbols are out of the scope a module binds against.  The module
loaded and then killed the process at the first call it could not bind, so
AXIS died outright where the preview reached a G53.7 on a genserkins
machine.  Putting the library in the global scope first is enough.
The preview has a machine to ask, through the status buffer it already
reads for the tool table and the offsets, so it can answer for the joints
as well.  Without them every inverse started from zeros, which previewed a
point-to-point move from a pose the machine is not standing in, and on a
serial arm could not be inverted at all.

The one method on the mixin covers AXIS, gremlin and the Qt screens, which
all take their canon from it.
@grandixximo

Copy link
Copy Markdown
Contributor Author

@Sigma1912 the load is fixed too, pushed as 146ef1c41e.

Two things were in the way. The preview had no machine to ask where the joints stand, so it started every inverse from zeros like the interpreter did; it now asks the canon, which already reads the status buffer for the tool table and the offsets. And a kinematics module loaded in a GUI could not reach the HAL library it calls, so on genserkins the failed inverse took AXIS down with it rather than reporting. Both AXIS and the gremlin and Qt screens pick the change up, since they share the one canon mixin.

Worth a look on melfa-sim-three21 as well: there the preview never complained, it just previewed the moves from a pose the machine was not standing in.

The loader built the path from EMC2_HOME and a fixed "rtlib", which is
where a run-in-place build keeps its modules and nowhere near where an
installed one does.  Everything else that loads a module goes by
EMC2_RTLIB_DIR, which is the directory the modules are installed into, so
go by that here too.
pumakins answers the inverse in closed form and never looks at the joints
it is handed, so tests/ptp-robot says nothing about the seed.  genserkins
takes the Jacobian at those joints: the machine here homes well away from
all zeros, so the seed has to come from where the machine stands, and all
zeros is the singular pose it cannot come from at all.

The same program goes through the interpreter and through the preview, and
the two have to agree on where it ends up.  Then the arm is parked in the
singular pose, where the module reports through the HAL library it prints
with: the preview refuses, rather than the process going down.
Neither test builds anything, so the guard that keeps the tests needing
build tools out of a package run has nothing to do here.  Both pass
against installed packages, and the module path they exercise is the one
that only an installed build gets wrong.
@Sigma1912

Copy link
Copy Markdown
Contributor

Thanks for the update. Works nicely with the analytical three21 kinematics but has problems with the iterative genserkins. (I've attached the test program below.)

Details

Testing with configs/sim/axis/vismach/melfa-sim/melfa-sim-three21 works nicely:

Screenshot from 2026-09-09 09-36-33

Testing with configs/sim/axis/vismach/melfa-sim/melfa-sim-genser fails with the following error (Line17: G0 Y150 but this changes to other lines if that one is removed ):

USRMOT: ERROR: invalid command
Linear move on line 17 would exceed joint 2's positive limit max:[76.000000]
Linear move on line 17 would exceed joint 4's negative limit min:[-120.000000]
invalid params in linear command

(Set the start pose to make sure the kinematics can handle it)
G90
G53.7 G0 J0=0 J1=-90 J2=0 J3=0 J4=90
(Go to start coordinates)
G53.4 G0 X450 Y200 Z350 B10 C45
(Rectangle)
G91
G0 Y150
X150
Y-150
X-150
(Go different pose)
G90
G53.7 G0 J0=90 J1=-90 J2=0 J3=0 J4=90
(Rectangle)
G91
G0 Y150
X150
Y-150
X-150
(Go to different coordinates)
G90
G53.4 G0 X450 Y-200 Z150 B0 C0
(Rectangle)
G91
G0 Y150
X150
Y-150
X-150
(Go to different coordinates)
G90
G53.4 G0 X50 Y-600 Z150 B10 C-80 a90
(Rectangle)
G91
G0 Y150
X150
Y-150
X-150
(Go to start pose)
G90
G53.7 G0 J0=0 J1=-90 J2=0 J3=0 J4=90
(Go different pose)
G90
G53.7 G0 J0=90 J1=-40 J2=-40 J3=0 J4=90
M2

@grandixximo

Copy link
Copy Markdown
Contributor Author

I'll look into it, I found another issue with G12.1-G13, I had to re-stack all my commits to keep git history somewhat clean, will push the re-stacked branch soon, then will look at a fix for genser...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants